<?php
// +----------------------------------------------------------------------
// | 在我们年轻的城市里，没有不可能的事！
// +----------------------------------------------------------------------
// | Copyright (c) 2020 http://srs.micang.com All rights reserved.
// +----------------------------------------------------------------------
// | Author : Jansen <jansen.shi@qq.com>
// +----------------------------------------------------------------------
namespace jansen\srs\drivers;
use Metaregistrar\EPP\eppException;
use Metaregistrar\EPP\eppRequest;
use Metaregistrar\EPP\eppPollRequest;
use Metaregistrar\EPP\verisignEppConnection;
abstract class Registry{
    protected $response;
    private $eppClient;
    /**
     * Registry constructor.
     *
     * @param bool $logging
     */
    public function __construct(bool $logging=true){
        if (!($this->eppClient instanceof verisignEppConnection)){
            $this->eppClient = new verisignEppConnection($logging);
        }
    }
    /**
     * 设置服务器信息
     * @param string $host
     * @param int    $port
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function setHost(string $host, int $port=700):void{
        $this->eppClient->setHostname($host);
        $this->eppClient->setPort($port);
    }
    /**
     * 设置连接账户信息
     * @param string $username
     * @param string $password
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function setAccount(string $username, string $password):void{
        $this->eppClient->setUsername($username);
        $this->eppClient->setPassword($password);
    }
    /**
     * 启用证书连接
     * @param string $certfile
     * @param string $passphrase
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function enableCertification(string $certfile, string $passphrase=null):void{
        $this->eppClient->enableCertification($certfile, $passphrase);
    }
    /**
     * 启用自签名证书
     * @param bool $selfsigned
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function enableAllowSelfSigned():void{
        $this->eppClient->setAllowSelfSigned(true);
    }
    /**
     * 禁用peer_name验证
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function disableVerifyPeerName():void{
        $this->eppClient->setVerifyPeerName(false);
    }
    /**
     * 设置日志文件路径
     * @param string $logpath 支持目录或具体文件
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function setLogFile(string $logpath):void{
        if (empty(pathinfo($logpath, PATHINFO_EXTENSION))){
            $this->eppClient->setLogFile($logpath.(strrchr($logpath, '/')?'':'/').'epp'.date('Ymd').'.log');
        }else{
            $this->eppClient->setLogFile($logpath);
        }
    }
    /**
     * 设置连接服务器的超时时间
     * @param int $timeout
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function setTimeout(int $timeout):void{
        $this->eppClient->setTimeout($timeout);
    }
    /**
     * 修改EPP密码
     * @param string $password
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function resetEppPassword(string $password):bool{
        $this->eppClient->setNewPassword($password);
        if (!$this->eppClient->login(true)){
            throw new eppException('修改EPP登录密码失败。', E_USER_ERROR);
        }
        return true;
    }
    /**
     * 获取、确认POLL消息
     * @param string $id
     * @return array|bool
     * @throws \Metaregistrar\EPP\eppException
     * @author:Jansen <jansen.shi@qq.com>
     */
    public function notify(string $id=null){
        $action = empty($id) ? eppPollRequest::POLL_REQ : eppPollRequest::POLL_ACK;
        $eppRequest = new eppPollRequest($action, $id?:null);
        $this->request($eppRequest);
        if ($action == eppPollRequest::POLL_ACK)    return true;
        if ($this->response->getMessageCount() == 0)    return null;
        $result = [];
        $result['queue']['count'] = $this->response->getMessageCount();
        $result['queue']['id'] = $this->response->getMessageId();
        $result['queue']['object'] = $this->response->getObjectType();
        $result['data']['type'] = $this->response->getMessageType();
        $result['data']['id'] = $this->response->getId();
        $result['data']['name'] = $this->response->getName();
        $result['data']['status'] = $this->response->getStatus();
        $result['data']['status_text'] = $this->response->getStatusText();
        $result['data']['transfer_status'] = $this->response->getTransferStatus();
        $result['data']['rgp_status'] = $this->response->getRgpStatus();
        $result['data']['request_date'] = $this->response->getRequestDate();
        $result['data']['expired_date'] = $this->response->getExpirationDate();
        $result['data']['action_date'] = $this->response->getActionDate();
        $result['data']['report_due_date'] = $this->response->getReportDueDate();
        return $result;
    }
    //==================== 域名操作相关 ====================
    /**
     * 检查域名是否可注册
     * @param string $domain 待查询域名
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainCheck(string $domain):bool;
    /**
     * 查询域名信息
     * @param string $domain    待查询域名
     * @param string $password  密码
     * @param string $hosts     查询范围
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainInfo(string $domain, string $password=null, string $hosts='all'):array;
    /**
     * 注册域名
     * @param string $domain        待注册域名
     * @param int    $period        年限，1-10
     * @param string $contactId     联系人ID
     * @param array  $ns            NS服务器列表
     * @param string $rnvc          实名认证验证码
     * @param string $dnvc          命名认证验证码
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainCreate(string $domain, int $period, string $contactId=null, array $ns=[], string $rnvc=null, string $dnvc=null):array;
    /**
     * 删除域名
     * @param string $domain    待删除域名
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainDelete(string $domain):bool;
    /**
     * 续费域名
     * @param string $domain    待续费域名
     * @param string $date      未续时的到期日期，防止短时间多次续费
     * @param int    $period    年限，1-10
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainRenew(string $domain, string $date, int $period):array;
    /**
     * 更新域名信息
     * @param string $domain    待更新域名
     * @param array  $contact   联系人信息[registrant/admin/tech/billing]
     * @param array  $status    域名注册商状态
     * @param array  $ns        域名服务器
     * @param bool   $resetPwd  是否重置域名密码，默认为false
     * @return bool
     * @todo EPP的Update命令支持修改和移除域名密码，当前方法仅支持修改密码，不支持移除
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainUpdate(string $domain, array $contact=[], array $status=[], array $ns=[], bool $resetPwd=false):bool;
    /**
     * 转移域名[发起、查询、中止、拒绝、同意]
     * 作为转入方，可执行：发起、查询、中止
     * 作为转出方，可执行：查询、拒绝、同意
     * @param string $op 操作类型，request:发起请求，query:查询进度，cancel:中止转移，approve:同意转移，reject:拒绝转移
     * @param string $domain 待转移域名
     * @param string $password 密码
     * @param int $period 转移后延长的年限，1-10
     * @param string|null $rnvc
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainTransfer(string $op, string $domain, string $password, int $period=1, string $rnvc=null):array;
    /**
     * 域名赎回
     * 需要调用两次，第一次op=request，第二次op=report
     * @param string $op        操作：request发起请求，report提交报告
     * @param string $domain    待赎回域名
     * @param string $expire    赎回前的到期时间
     * @param string $reason    赎回原因
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainRestore(string $op, string $domain, string $expire=null, string $reason=null):bool;
    /**
     * 域名实名
     * @param string $domain 待实名域名
     * @param string $rnvc   实名验证码
     * @param string $dnvc   命名验证码
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function domainRealName(string $domain, string $rnvc, string $dnvc=null):bool;
    //==================== 联系人操作相关 ====================
    /**
     * 检查联系人是否存在
     * @param string $contactId 待查联系人ID
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function contactCheck(string $contactId);
    /**
     * 查询联系人信息
     * @param string $contactId 待查联系人ID
     * @param string $password  密码
     * @return mixed
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function contactInfo(string $contactId, string $password):array;
    /**
     * 创建联系人
     * @param string      $name     姓名
     * @param string      $province 省份
     * @param string      $city     城市
     * @param string      $street   街道地址
     * @param string      $zipcode  邮编
     * @param string      $voice    电话、手机
     * @param string      $org      组织名称
     * @param string      $type     地址类型，int:国际化地址信息，地址只能用英文或拼音，loc:本地地址信息，国内使用中文
     * @param string      $email    邮箱
     * @param string      $fax      传真
     * @param string      $password 密码
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function contactCreate(string $name, string $province, string $city, string $street, string $zipcode, string $voice, string $org=null, string $type='int', string $email=null, string $fax=null, string $password=null):array;
    /**
     * 删除联系人
     * @param string $contactId 联系人ID
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function contactDelete(string $contactId):bool;
    /**
     * 更新联系人信息
     * @param string $contactId 联系人ID
     * @param array  $postal    邮政信息
     * @param string $voice     电话或手机
     * @param string $email     邮箱
     * @param string $fax       传真
     * @param array  $status    状态
     * @todo EPP的Update命令支持修改和移除联系人密码，当前方法暂未支持
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function contactUpdate(string $contactId, array $postal=[], string $voice=null, string $email = null, string $fax = null, array $status=[]):bool;
    /**
     * 转移联系人[发起、查询、中止、拒绝、同意]
     * 作为转入方，可执行：发起、查询、中止
     * 作为转出方，可执行：查询、拒绝、同意
     * @param string $op        操作类型，request:发起请求，query:查询进度，cancel:中止转移，approve:同意转移，reject:拒绝转移
     * @param string $contactId 联系人ID
     * @param string $password  密码
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function contactTransfer(string $op, string $contactId, string $password):void;
    //==================== 名称服务器操作相关 ====================
    /**
     * 检查名称服务器是否存在
     * @param string $host  主机名
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function nsCheck(string $host);
    /**
     * 查询名称服务器信息
     * @param string $host  主机名
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function nsInfo(string $host);
    /**
     * 创建名称服务器
     * @param string $host  主机名
     * @param array  $ip    ip地址，支持IPv4和IPv6
     * @return array
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function nsCreate(string $host, array $ip);
    /**
     * 删除名称服务器
     * @param string $host  主机名
     * @return bool
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function nsDelete(string $host);
    /**
     * 变更名称服务器信息
     * @param string      $host     主机名
     * @param string|null $name     新主机名
     * @param array       $ip       IP
     * @param array       $status   状态
     * @return mixed
     * @author:Jansen <jansen.shi@qq.com>
     */
    abstract public function nsUpdate(string $host, string $name=null, array $ip=[], array $status=[]);
    /**
     * 统一的请求发起方法
     * @param eppRequest $eppRequest
     * @throws \Metaregistrar\EPP\eppException
     * @author:Jansen <jansen.shi@qq.com>
     */
    protected function request(eppRequest $eppRequest):void{
        //是否已登录，未登录时，先做登录操作
        if (!$this->eppClient->isLoggedin() && !$this->eppClient->login(true)){
            throw new eppException('登录EPP接口失败。');
        }
        //登录成功后，才能发起请求
        $this->response = $this->eppClient->request($eppRequest);
    }
    /**
     * 生成联系人ID
     * @return string
     */
    protected function generateContactId():string{
        $chars = array('A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','W','X','Y','Z');
        shuffle($chars);
        return date('ymdHi').implode('', array_slice($chars, 0, 5));
    }
    /**
     * 生成复杂密码
     * @param int $length 生成的密码长度
     * @return string
     * @author:Jansen <jansen.shi@qq.com>
     */
    protected function generatePassword(int $length=12):string{
        $alpha = ['a','b','c','d','e','f','g','h','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z'];
        $number = ['0','1','2','3','4','5','6','7','8','9'];
        $symbol = ['@','#','$','%','*','+','=','_','-','^','~'];
        $symbolNum = rand(2, 3);
        $charNum = ($length - $symbolNum) / 2;
        //打乱符号数组
        shuffle($symbol);
        shuffle($alpha);
        shuffle($number);
        //随机取字符
        $result['symbol'] = array_slice($symbol, 0, $symbolNum);
        $result['number'] = array_slice($number, 0, $charNum);
        $result['alpha'] = array_slice($alpha, 0, $length-$charNum-$symbolNum);
        //合并
        $password = array_merge([], $result['symbol'], $result['alpha'], $result['number']);
        shuffle($password);
        return implode('', $password);
    }
    /**
     * 检查是否IDN域名
     * @param string $domain
     * @return bool
     */
    protected function isIDN(string $domain):bool{
        return !(idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46) === $domain);
    }
}